﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
//using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage.RetryPolicies;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Runtime.Serialization.Formatters.Binary;

namespace cloudMPI
{
    public class BlobHelper
    {
        public CloudStorageAccount Account;
        public CloudBlobClient BlobClient;
        public string connectionstring;

        // Constructor - pass in a storage connection string.

        public BlobHelper(string connectionString)
        {
            Account = CloudStorageAccount.Parse(connectionString);
            BlobClient = Account.CreateCloudBlobClient();
            BlobClient.RetryPolicy = new LinearRetry(TimeSpan.Zero, 4);
        }

        // Enumerate the containers in a storage account.
        // Return true on success, false if already exists, throw exception on error.

        public bool ListContainers(out List<CloudBlobContainer> containerList)
        {
            containerList = new List<CloudBlobContainer>();

            try
            {
                IEnumerable<CloudBlobContainer> containers = BlobClient.ListContainers();
                if (containers != null)
                {
                    containerList.AddRange(containers);
                }
                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    return false;
                }
                else
                {
                    throw;
                }
            }
        }

        public void BlobStreamDownload(string containerName, string blobName, string FileName)
        {
            CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
            CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
            using (FileStream fs = new FileStream(FileName, FileMode.Create)) 
            { 
                blob.DownloadToStream(fs); 
            }
        }
        // Create a blob container. 
        // Return true on success, false if already exists, throw exception on error.
        public bool CreateContainer(string containerName)
        {
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                container.CreateIfNotExists();
                return true;
            }
            catch (StorageException ex)
            {
                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("Storage Exception in Create Container" + ex.Message);
                    return false;
                }
                throw;
            }
        }
        // Delete a blob container. 
        // Return true on success, false if not found, throw exception on error.
        public bool DeleteContainer(string containerName)
        {
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                container.Delete();
                return true;
            }
            catch (StorageException ex)
            {
                throw;
            }
        }
        // Enumerate the blobs in a container. 
        // Return true on success, false if already exists, throw exception on error.
        public bool ListBlobs(string containerName, out List<CloudBlockBlob> blobList)
        {
            blobList = new List<CloudBlockBlob>();

            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                IEnumerable<IListBlobItem> blobs = container.ListBlobs();
                if (blobs != null)
                {
                    foreach (CloudBlockBlob blob in blobs)
                    {
                        blobList.Add(blob);
                    }
                }
                return true;
            }
            catch (StorageException ex)
            {
                throw;
            }
        }
        // Put (create or update) a blob.
        // Return true on success, false if unable to create, throw exception on error.
        public bool PutLargeBlob(string connectionstring, string containerName, string blobName, string content)
        {
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
                int contentlength = content.Length;
                int toRead = 0;
                int charRead = 0;

                string blobcontent = content;
                List<string> blockIds = new List<string>();


                while (contentlength > 0)
                {
                    toRead = Math.Min(contentlength, DataConstants.MAX_BLOB_SIZE);
                    try
                    {
                        UTF8Encoding utf8Encoding = new UTF8Encoding();
                        using (MemoryStream memoryStream = new MemoryStream(utf8Encoding.GetBytes(blobcontent.ToCharArray(), charRead, toRead)))
                        {
                            string blockID = Guid.NewGuid().ToString();
                            string blockIdBase64 = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(blockID));
                            memoryStream.Position = 0;
                            blob.PutBlock(blockIdBase64, memoryStream, null);
                            blockIds.Add(blockIdBase64);
                        }
                        charRead += toRead;
                        contentlength -= toRead;
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Trace.WriteLine("Error in PutBlob" + ex.Message);
                        throw;
                    }
                }
             
                BlobHelper.PutBlockList(connectionstring, containerName, blobName, blockIds.ToArray());
                blockIds.Clear();
             
                return true;
            }
            catch (StorageException ex)
            {
                /*if ((int)ex.StatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("Storage Exception in PutBlob" + ex.Message);

                    //return false;
                }*/
                System.Diagnostics.Trace.WriteLine("Error in PutBlob" + ex.Message);
                throw;
            }
        }
        
        /* Store a Blob of size up to 64 MB */
        // Put (create or update) a blob.
        // Return true on success, false if unable to create, throw exception on error.
        public bool PutBlob(string containerName, string blobName, string content)
        {
           
            CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
            CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
            string blobcontent = content;
            List<string> blockIds = new List<string>();

            if (content.Length > DataConstants.MAX_BLOB_SIZE)
            {
                return PutLargeBlob(connectionstring, containerName, blobName, content);
            }
            try
            {
                blob.UploadFromStream(new MemoryStream(Encoding.ASCII.GetBytes(content)));
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine("Error in PutBlob" + ex.Message);
                throw;
            }
            
            return true;
        }
        

        // Retrieve the list of uploaded blocks for a blob. 
        // Return true on success, false if already exists, throw exception on error.
        public static bool GetBlockList(string connectionstring, string containerName, string blobName, out string[] blockIds)
        {
            blockIds = null;

            try
            {
                CloudStorageAccount account = CloudStorageAccount.Parse(connectionstring);
                CloudBlobClient blobClient = account.CreateCloudBlobClient();
                CloudBlobContainer container = blobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

                IEnumerable<ListBlockItem> blobs = blob.DownloadBlockList(BlockListingFilter.Uncommitted);
                blockIds = new string[blobs.Count()];
                int i = 0;
                foreach (ListBlockItem block in blobs)
                {
                    blockIds[i++] = block.Name;
                }
                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in get blocklist" + ex.Message);
                    return false;
                }
                
                throw;
            }
        }
        // Put block - upload a block (portion) of a blob. 
        // Return true on success, false if already exists, throw exception on error.
        public static bool PutBlock(string connectionstring, string containerName, string blobName, string blockId, string content)
        {
            if (content.Length > 2 * 1000 * 1000)
            {
                return PutLargeBlock(connectionstring, containerName, blobName, blockId, content);
            }

            try
            {
                CloudStorageAccount account = CloudStorageAccount.Parse(connectionstring);
                CloudBlobClient blobClient = account.CreateCloudBlobClient();
                CloudBlobContainer container = blobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

                string blockIdBase64 = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(blockId));

                if (content == "") content = " ";
                UTF8Encoding utf8Encoding = new UTF8Encoding();
                using (MemoryStream memoryStream = new MemoryStream(utf8Encoding.GetBytes(content)))
                {
                    memoryStream.Position = 0;
                    blob.PutBlock(blockIdBase64, memoryStream, null);
                }

                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in Putblock" + ex.Message);
                    return false;
                }
                System.Diagnostics.Trace.WriteLine("Error in PutBlock" + ex.Message);
                throw;
            }
        }
        public static bool PutBlock(string connectionstring, string containerName, string blobName, string blockId, byte[] content)
        {

            try
            {
                CloudStorageAccount account = CloudStorageAccount.Parse(connectionstring);
                CloudBlobClient blobClient = account.CreateCloudBlobClient();
                CloudBlobContainer container = blobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

                string blockIdBase64 = System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(blockId));

                UTF8Encoding utf8Encoding = new UTF8Encoding();
                using (MemoryStream memoryStream = new MemoryStream(content))
                {
                    memoryStream.Position = 0;
                    blob.PutBlock(blockIdBase64, memoryStream, null);
                }

                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in Putblock" + ex.Message);
                    return false;
                }
                System.Diagnostics.Trace.WriteLine("Error in PutBlock" + ex.Message);
                throw;
            }
        }
        // Put Large block - upload a greater than 4 MB block (portion) of a blob. 
        // Return true on success, false if already exists, throw exception on error.

        public static bool PutLargeBlock(string connectionstring, string containerName, string blobName, string blockId, string content)
        {
            int contentlength = content.Length;
            int toRead = 0;
            int charRead = 0;
            bool result = true;
            while (contentlength > 0)
            {
                toRead = Math.Min(contentlength, 2 * 1000 * 1000);
                try
                {
                    result = PutBlock(connectionstring, containerName, blobName, Guid.NewGuid().ToString(), content.Substring(charRead, toRead));
                    charRead += toRead;
                    contentlength -= toRead;
                }
                catch (StorageException ex)
                {

                    if ((int)ex.RequestInformation.HttpStatusCode == 404)
                    {
                        System.Diagnostics.Trace.WriteLine("404 Exception in PutLargeblock" + ex.Message);
                        return false;
                    }
                    System.Diagnostics.Trace.WriteLine("Error in PutLargeBlock:");
                    throw;
                }
            }
            return result;
        }

        public bool PutBlock(string containerName, string blobName, int blockId, string[] blockIds, byte[] content, int contentSize)
        {
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

                string blockIdBase64 = Convert.ToBase64String(System.BitConverter.GetBytes(blockId));

                UTF8Encoding utf8Encoding = new UTF8Encoding();
                using (MemoryStream memoryStream = new MemoryStream(content, 0, contentSize))
                {
                    blob.PutBlock(blockIdBase64, memoryStream, null);
                }

                blockIds[blockId] = blockIdBase64;

                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in Putblock" + ex.Message);
                    return false;
                }
                System.Diagnostics.Trace.WriteLine("Error in PutBlock:");
                throw;
            }
        }

        // Put block list - complete creation of blob based on uploaded content.
        // Return true on success, false if already exists, throw exception on error.

        public static bool PutBlockList(string connectionstring, string containerName, string blobName, string[] blockIds)
        {
            try
            {
                CloudStorageAccount account = CloudStorageAccount.Parse(connectionstring);
                CloudBlobClient blobClient = account.CreateCloudBlobClient();
                CloudBlobContainer container = blobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

                blob.PutBlockList(blockIds);

                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in PutblockList" + ex.Message);
                    return false;
                }
                System.Diagnostics.Trace.WriteLine("Error in PutBlockList:");
                throw;
            }
        }

        // Get (retrieve) a blob and return its content.
        // Return true on success, false if unable to create, throw exception on error.

        public bool GetBlob(string containerName, string blobName, out string content)
        {
            content = null;
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
                
                using (var memoryStream = new MemoryStream())
                {
                    blob.DownloadToStream(memoryStream);
                    memoryStream.Seek(0, SeekOrigin.Begin);
                    content = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
                }
                
                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in GetBlob" + ex.Message);
                    return false;
                }

                throw;
            }
        }
        // Delete a blob.
        // Return true on success, false if unable to create, throw exception on error.
        //We do not plan on using PageBlobs as of now; if needed we wll have to create separate function for page blob deletion
        public bool DeleteBlob(string containerName, string blobName)
        {
            try
            {
                CloudBlobContainer container = BlobClient.GetContainerReference(containerName);
                CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
                blob.DeleteIfExists();
                return true;
            }
            catch (StorageException ex)
            {

                if ((int)ex.RequestInformation.HttpStatusCode == 404)
                {
                    System.Diagnostics.Trace.WriteLine("404 Exception in Delete Blob" + ex.Message);
                    return false;
                }

                throw;
            }
        }
        static public string EncodeTo64(string toEncode)
        {

            byte[] toEncodeAsBytes

                  = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);

            string returnValue

                  = System.Convert.ToBase64String(toEncodeAsBytes);

            return returnValue;

        }
        static public string DecodeFrom64(string encodedData)
        {

            byte[] encodedDataAsBytes

                = System.Convert.FromBase64String(encodedData);

            string returnValue =

               System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes);

            return returnValue;

        }
    }
   

}
